package com.rajaev.dtu.tractor.domain.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import com.google.common.util.concurrent.AtomicDouble;
import com.mongodb.client.MongoCursor;
import com.rajaev.dtu.tractor.app.service.StatisticService;
import com.rajaev.dtu.tractor.domain.entity.BatteryElectricLog;
import com.rajaev.dtu.tractor.domain.entity.BatteryOnlineLogPo;
import com.rajaev.dtu.tractor.domain.entity.BatteryPo;
import com.rajaev.dtu.tractor.domain.entity.BatteryRunInfoResult;
import com.rajaev.dtu.tractor.domain.repository.mysql.BatteryDao;
import com.rajaev.dtu.tractor.domain.repository.mysql.BatteryElectricDao;
import com.rajaev.dtu.tractor.domain.repository.mysql.BatteryOnlineLogDao;
import com.rajaev.dtu.tractor.domain.vo.BmsDailyStaticResult;
import com.rajaev.dtu.tractor.domain.vo.BmsHourlyStaticResult;
import com.rajaev.dtu.tractor.domain.vo.StatisticResultVo;
import com.rajaev.dtu.tractor.infrastructure.base.common.Constants;
import com.rajaev.dtu.tractor.infrastructure.helper.MongoDbUtils;
import org.apache.commons.collections4.IteratorUtils;
import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.mongodb.client.model.Filters.*;

/**
 * @Created with IDEA
 * @author: tianjie
 * @Date: 2019/6/14
 * @Time: 17:00
 */
@Service
public class StatisticServiceImpl implements StatisticService {

    @Autowired
    private MongoDbUtils mongoDbUtils;

    @Autowired
    BatteryDao batteryDao;



    @Autowired
    CodecRegistry codecRegistry;

    @Override
    public BatteryRunInfoResult getBatteryRunInfoResultFromMongo(String vin) {
        BatteryRunInfoResult.BatteryRunInfoResultBuilder batteryRunInfoResultBuilder = BatteryRunInfoResult.builder().vin(vin);
        AtomicDouble totalDischarge = new AtomicDouble(0d);
        AtomicDouble totalCharge = new AtomicDouble(0d);
        AtomicLong totalOnlineTime = new AtomicLong(0);
        AtomicLong totalDisChargeTime = new AtomicLong(0);

        AtomicInteger totalDays = new AtomicInteger();
        MongoCursor<BmsDailyStaticResult> dailyStaticResultMongoCursor = mongoDbUtils.getDatabase().getCollection(Constants.dailyStaticCollectionName).withCodecRegistry(codecRegistry)
                .find(eq("vin", vin), BmsDailyStaticResult.class).iterator();


        IteratorUtils.toList(dailyStaticResultMongoCursor).stream().forEach(bmsDailyStaticResult -> {
            totalDischarge.addAndGet(bmsDailyStaticResult.getDischargeElectricity());
            totalCharge.addAndGet(bmsDailyStaticResult.getChargeElectricity());
            totalOnlineTime.addAndGet(bmsDailyStaticResult.getOnlineTime());
            totalDisChargeTime.addAndGet(bmsDailyStaticResult.getDischargeTime());
            totalDays.incrementAndGet();
        });

        //todo  当天的充电 放电和 上线时间统计。

        /*
        MongoCursor<BmsHourlyStaticResult> hourlyStaticResultMongoCursor = mongoDbUtils.getDatabase().getCollection(Constants.hourlyStaticCollectionName).withCodecRegistry(codecRegistry)
                .find(and(eq("vin", vin), gt("dataDateHour", Integer.parseInt(new SimpleDateFormat("yyyyMMdd").format(new Date()) + "00"))), BmsHourlyStaticResult.class)
                .iterator();

        IteratorUtils.toList(hourlyStaticResultMongoCursor).forEach(bmsHourlyStaticResult -> {
            totalDischarge.addAndGet(bmsHourlyStaticResult.getDischargeElectricity());
            totalCharge.addAndGet(bmsHourlyStaticResult.getChargeElectricity());
            totalOnlineTime.addAndGet(bmsHourlyStaticResult.getOnlineTime());
        });
        */

        batteryRunInfoResultBuilder.totalCharge(totalCharge.floatValue())
                .totalDischarge(totalDischarge.floatValue())
                .totalOnlineTime(totalOnlineTime.get())
                .totalDischargeTime(totalDisChargeTime.get());

        batteryRunInfoResultBuilder.dailyAvgDischarge(totalDays.get() == 0 ? 0 : totalDischarge.floatValue() / Float.valueOf(totalDays.get()));
        return batteryRunInfoResultBuilder.build();
    }

    @Override
    public StatisticResultVo queryAvgChargeInfoByProjectTeam(Integer startDate, Integer endDate, String[] projectTeamIdArray) throws ExecutionException, InterruptedException {
        List<BatteryPo> list = batteryDao.selectAllBattery();
        String[] vinArray =   list.stream().filter(batteryPo -> ArrayUtil.contains(projectTeamIdArray,String.valueOf(batteryPo.getProjectTeamId()) )).map(batteryPo -> batteryPo.getVin()).collect(Collectors.toList()).toArray(new String[]{});
        return queryStatisticResultVoByVinArray(vinArray,startDate,endDate);
    }


    @Override
    public StatisticResultVo queryAvgChargeInfoByCustomerId(Integer startDate, Integer endDate, String[] customerIdArray) throws ExecutionException, InterruptedException {
        List<BatteryPo> list = batteryDao.selectAllBattery();
        String[] vinArray =   list.stream().filter(batteryPo -> ArrayUtil.contains(customerIdArray,String.valueOf(batteryPo.getCustomerId()) )).map(batteryPo -> batteryPo.getVin()).collect(Collectors.toList()).toArray(new String[]{});
        return queryStatisticResultVoByVinArray(vinArray,startDate,endDate);
    }

    @Override
    public StatisticResultVo queryAvgChargeInfo(Integer startDate, Integer endDate, String vin) throws ExecutionException, InterruptedException {
        return queryStatisticResultVoByVinArray(new String[]{vin},startDate,endDate);
    }

    /**
     *
     * @param vinArray vin数组
     * @param startDate yyyyMMdd
     * @param endDate yyyyMMdd
     * @return
     */
    public StatisticResultVo queryStatisticResultVoByVinArray(String[] vinArray,Integer startDate, Integer endDate) throws ExecutionException, InterruptedException {
        //根据日期区间，统计日均数据
        CompletableFuture<List<StatisticResultVo.DailyAvgInfo>> dailyAvgInfoListFuture = CompletableFuture.supplyAsync(() -> statisticDailyInfo(startDate, endDate, vinArray));
        //统计耗电前十的电池
        CompletableFuture<List<StatisticResultVo.Battery>> top10DischargeBatteriesFuture = CompletableFuture.supplyAsync(() -> top10Batteries(vinArray, startDate, endDate, StatisticResultVo.ElectricType.DISCHARGE));
        //统计充电前十的电池
        CompletableFuture<List<StatisticResultVo.Battery>> top10ChargeBatteriesFuture = CompletableFuture.supplyAsync(() -> top10Batteries(vinArray, startDate, endDate, StatisticResultVo.ElectricType.CHARGE));

        //统计当日的耗电信息
        CompletableFuture<StatisticResultVo.TodayInfo> todayInfoFuture = CompletableFuture.supplyAsync(() -> statisticTodayInfo(vinArray));

        int totalBatteries = vinArray.length;
        int onlineDays = calculateOnlineDays(dailyAvgInfoListFuture.get());
        BigDecimal avgDischarge = calculateAvgDischarge(dailyAvgInfoListFuture.get(), onlineDays);
        BigDecimal avgCharge = calculateAvgCharge(dailyAvgInfoListFuture.get(), onlineDays);

        BigDecimal avgOnlineTime = calculateAvgOnlineTime(dailyAvgInfoListFuture.get(), onlineDays);
        BigDecimal avgOnlineRatio = BigDecimal.valueOf(calculateAvgOnlineRatio(dailyAvgInfoListFuture.get()));
        BigDecimal baseDischarge = avgDischarge.multiply(BigDecimal.valueOf(totalBatteries));
        BigDecimal baseCharge = avgCharge.multiply(BigDecimal.valueOf(totalBatteries));

        StatisticResultVo statisticResultVo = new StatisticResultVo();
        statisticResultVo.setStartDate(startDate);
        statisticResultVo.setEndDate(endDate);
        statisticResultVo.setTodayInfo(todayInfoFuture.get());
        statisticResultVo.setTop10DischargeList(top10DischargeBatteriesFuture.get());
        statisticResultVo.setTop10ChargeList(top10ChargeBatteriesFuture.get());
        statisticResultVo.setDailyAvgInfoList(dailyAvgInfoListFuture.get());
        statisticResultVo.setAvgDischarge(avgDischarge);
        statisticResultVo.setAvgOnlineTime(avgOnlineTime);
        statisticResultVo.setAvgOnlineRatio(avgOnlineRatio);
        statisticResultVo.setBaseDischarge(baseDischarge);
        statisticResultVo.setBaseCharge(baseCharge);
        return statisticResultVo;
    }


    public int calculateOnlineDays(List<StatisticResultVo.DailyAvgInfo> dailyAvgInfoList) {
        int count = (int) dailyAvgInfoList.stream().filter(dailyAvgInfo -> dailyAvgInfo.getOnlineRatio() > 0).count();
        return count;
    }

    /**
     * 统计平均的耗电量，不计算在线率为0 的天数
     *
     * @param dailyAvgInfoList
     * @return
     */
    public BigDecimal calculateAvgDischarge(List<StatisticResultVo.DailyAvgInfo> dailyAvgInfoList, int onlineDays) {
        AtomicReference<BigDecimal> totalDischargeReference = new AtomicReference<>(new BigDecimal(0));
        dailyAvgInfoList.stream()
                .filter(dailyAvgInfo -> dailyAvgInfo.getOnlineRatio() > 0)
                .map(StatisticResultVo.DailyAvgInfo::getAvgDischarge)
                .forEach(avgDischarge -> totalDischargeReference.updateAndGet(bigDecimal -> bigDecimal.add(avgDischarge)));
        return onlineDays == 0 ? BigDecimal.ZERO : totalDischargeReference.get().divide(BigDecimal.valueOf(onlineDays), 2, BigDecimal.ROUND_DOWN);
    }

    /**
     * 计算平均的充电量
     *
     * @param dailyAvgInfoList
     * @param onlineDays
     * @return
     */
    public BigDecimal calculateAvgCharge(List<StatisticResultVo.DailyAvgInfo> dailyAvgInfoList, int onlineDays) {
        AtomicReference<BigDecimal> totalDischargeReference = new AtomicReference<>(new BigDecimal(0));
        dailyAvgInfoList.stream()
                .filter(dailyAvgInfo -> dailyAvgInfo.getOnlineRatio() > 0)
                .map(StatisticResultVo.DailyAvgInfo::getCharge)
                .forEach(discharge -> totalDischargeReference.updateAndGet(bigDecimal -> bigDecimal.add(discharge)));
        return onlineDays == 0 ? BigDecimal.ZERO : totalDischargeReference.get().divide(BigDecimal.valueOf(onlineDays), 2, BigDecimal.ROUND_DOWN);
    }

    /**
     * 统计平均的在线率，不计算在线率为0 的天数
     *
     * @param dailyAvgInfoList 每日运行数据
     * @return
     */
    public double calculateAvgOnlineRatio(List<StatisticResultVo.DailyAvgInfo> dailyAvgInfoList) {

        return dailyAvgInfoList.size() == 0 ? 0f : dailyAvgInfoList.stream()
                .filter(dailyAvgInfo -> dailyAvgInfo.getOnlineRatio() > 0)
                .mapToDouble(StatisticResultVo.DailyAvgInfo::getOnlineRatio).average().getAsDouble();
    }

    public BigDecimal calculateAvgOnlineTime(List<StatisticResultVo.DailyAvgInfo> dailyAvgInfoList, int onlineDays) {
        AtomicReference<BigDecimal> totalOnlinetime = new AtomicReference<>(new BigDecimal(0));
        dailyAvgInfoList.stream().filter(dailyAvgInfo -> dailyAvgInfo.getOnlineRatio() > 0)
                .map(StatisticResultVo.DailyAvgInfo::getAvgOnlineTime).forEach(onlineTime -> {
            totalOnlinetime.updateAndGet(bigDecimal -> bigDecimal.add(onlineTime));
        });
        return onlineDays == 0 ? BigDecimal.ZERO : totalOnlinetime.get().divide(BigDecimal.valueOf(onlineDays), 2, BigDecimal.ROUND_DOWN);

    }

    /**
     * 按天统计日期段内，每天的车辆的上线率，车均耗电量，充电总量，耗电总量等数据
     *
     * @param startDate
     * @param endDate
     * @param vinArray
     * @return List<StatisticResultVo.dailyAvgInfo>
     */
    private List<StatisticResultVo.DailyAvgInfo> statisticDailyInfo(Integer startDate, Integer endDate, String[] vinArray) {

        MongoCursor<BmsDailyStaticResult> dailyResults = mongoDbUtils.getDatabase().getCollection(Constants.dailyStaticCollectionName).withCodecRegistry(codecRegistry).find(
                and(lte("dataDate", endDate), gte("dataDate", startDate), in("vin", vinArray))
                , BmsDailyStaticResult.class).iterator();
        List<BmsDailyStaticResult> dailyResultList = IteratorUtils.toList(dailyResults);

        Map<Integer, List<BmsDailyStaticResult>> dailyResultMaps =
                dailyResultList.stream().collect(Collectors.groupingByConcurrent(BmsDailyStaticResult::getDataDate));
        List<StatisticResultVo.DailyAvgInfo> dailyAvgInfoList = new ArrayList<>();

        dailyResultMaps.forEach((dataDate, bmsDailyStaticResults) -> {
            int onlineCount = (int) bmsDailyStaticResults.stream().filter(bmsDailyStaticResult -> bmsDailyStaticResult.isOnline()).count();
            int offlineCount = (int) bmsDailyStaticResults.stream().filter(bmsDailyStaticResult -> !bmsDailyStaticResult.isOnline()).count();
            if (onlineCount != 0) {
                long dailyTotalOnlineTime = bmsDailyStaticResults.stream().mapToLong(BmsDailyStaticResult::getOnlineTime).sum();
                double dailyTotalCharge = bmsDailyStaticResults.stream().filter(bmsDailyStaticResult -> bmsDailyStaticResult.isOnline()).mapToDouble(BmsDailyStaticResult::getChargeElectricity).sum();
                double dailyTotalDischarge = bmsDailyStaticResults.stream().filter(bmsDailyStaticResult -> bmsDailyStaticResult.isOnline()).mapToDouble(BmsDailyStaticResult::getDischargeElectricity).sum();
                double avgDischarge = dailyTotalDischarge / onlineCount;
                StatisticResultVo.DailyAvgInfo dailyAvgInfo = new StatisticResultVo.DailyAvgInfo();
                dailyAvgInfo.setAvgDischarge(BigDecimal.valueOf(avgDischarge));
                dailyAvgInfo.setOnlineRatio(Float.valueOf(onlineCount) / Float.valueOf(onlineCount + offlineCount));
                dailyAvgInfo.setCharge(BigDecimal.valueOf(dailyTotalCharge));
                dailyAvgInfo.setDischarge(BigDecimal.valueOf(dailyTotalDischarge));
                dailyAvgInfo.setDataDate(dataDate);
                dailyAvgInfo.setAvgOnlineTime(BigDecimal.valueOf(Float.valueOf(dailyTotalOnlineTime) / Float.valueOf(onlineCount)));
                dailyAvgInfoList.add(dailyAvgInfo);
            }

        });


        return dailyAvgInfoList;
    }

    /**
     * 获取耗电量排名前十的车
     *
     * @param vinArray vin 数组
     * @param type        = 1,耗电，2：充电
     * @return
     */
    public List<StatisticResultVo.Battery> top10Batteries(String[] vinArray, Integer startDate, Integer endDate, final StatisticResultVo.ElectricType type) {

        //查询所有的历史数据
        MongoCursor<BmsDailyStaticResult> dailyResults = mongoDbUtils.getDatabase().getCollection(Constants.dailyStaticCollectionName).withCodecRegistry(codecRegistry).find(
                and(in("vin", vinArray), lte("dataDate", endDate), gte("dataDate", startDate)), BmsDailyStaticResult.class).iterator();

        List<BmsDailyStaticResult> dailyResultList = IteratorUtils.toList(dailyResults);

        //按照vin进行分组
        Map<String, List<BmsDailyStaticResult>> map = dailyResultList.stream().collect(Collectors.groupingBy(BmsDailyStaticResult::getVin));
        List<BatteryPo> allBatterylist = batteryDao.selectAllBattery();
        Iterator<String> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            String vin = iterator.next();
            if (allBatterylist.stream().noneMatch(batteryPo -> batteryPo.getVin().equalsIgnoreCase(vin))) {
                iterator.remove();
            }
        }
        //按照vin对应的耗电量的总和进行排序
        List<Map.Entry<String, List<BmsDailyStaticResult>>> list = sortMapByElectricity(map, type);
        List<Map.Entry<String, List<BmsDailyStaticResult>>> newlist = list.subList(0, list.size() >= 10 ? 10 : list.size());

        List<StatisticResultVo.Battery> top10Batteries = new ArrayList<>();

        newlist.forEach(m -> {
            String vin = m.getKey();
            BatteryPo batteryPo = batteryDao.selectByVin(vin);
            if (type.equals(StatisticResultVo.ElectricType.DISCHARGE)) {
                double totalDischarge = m.getValue().stream().mapToDouble(BmsDailyStaticResult::getDischargeElectricity).sum();
                StatisticResultVo.Battery battery = new StatisticResultVo.Battery(vin, type, batteryPo == null ? vin : batteryPo.getPlateNo(), BigDecimal.valueOf(totalDischarge));
                top10Batteries.add(battery);

            } else {
                double totalCharge = m.getValue().stream().mapToDouble(BmsDailyStaticResult::getChargeElectricity).sum();
                StatisticResultVo.Battery battery = new StatisticResultVo.Battery(vin, type, batteryPo == null ? vin : batteryPo.getPlateNo(), BigDecimal.valueOf(totalCharge));
                top10Batteries.add(battery);
            }
        });

        return top10Batteries;
    }

    private StatisticResultVo.TodayInfo statisticTodayInfo(String[] vinArray) {
        return null;

    }

    /**
     * 按每个车的耗电量从高到低排序
     *
     * @param map
     * @return
     */
    private static List<Map.Entry<String, List<BmsDailyStaticResult>>> sortMapByElectricity(Map<String, List<BmsDailyStaticResult>> map, StatisticResultVo.ElectricType type) {
        List<Map.Entry<String, List<BmsDailyStaticResult>>> list = new ArrayList<>(map.entrySet());
        Comparator<Map.Entry<String, List<BmsDailyStaticResult>>> comparator = (o1, o2) -> {
            if (type.equals(StatisticResultVo.ElectricType.DISCHARGE)) {
                Double discharge1 = o1.getValue().stream().mapToDouble(BmsDailyStaticResult::getDischargeElectricity).sum();
                Double discharge2 = o2.getValue().stream().mapToDouble(BmsDailyStaticResult::getDischargeElectricity).sum();
                return discharge1 - discharge2 >= 0 ? -1 : 1;
            } else if (type.equals(StatisticResultVo.ElectricType.CHARGE)) {
                Double charge1 = o1.getValue().stream().mapToDouble(BmsDailyStaticResult::getChargeElectricity).sum();
                Double charge2 = o2.getValue().stream().mapToDouble(BmsDailyStaticResult::getChargeElectricity).sum();
                return charge1 - charge2 >= 0 ? -1 : 1;
            } else {
                return 0;
            }
        };
        Collections.sort(list, comparator);
        return list;
    }
}
